﻿// <copyright file="ImportObjectsFromXml.cs" company="Codeplex project Eco.Contrib"> 
// The MIT License (MIT) 
// </copyright> 
// <author>Peter Buchmann</author> 

namespace Eco.Contrib
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Xml;
    using System.Xml.XPath;
    using Eco.Cache;
    using Eco.DataRepresentation;
    using Eco.Framework;
    using Eco.Handles;
    using Eco.ObjectRepresentation;
    using Eco.Persistence;
    using Eco.Services;
    using Eco.UmlRt;

    /// <summary>
    /// ImportObjectsFromXml: Imports objects from a XML file
    /// </summary>
    public class ImportObjectsFromXml : ImportExportBase
    {
        /// <summary>
        /// sourceCache  field
        /// </summary>
        private ICache sourceCache;

        /// <summary>
        /// sourceOrp  field
        /// </summary>
        private IObjectRepresentationProvider sourceOrp;

        /// <summary>
        /// stateService field
        /// </summary>
        private IStateService stateService;

        /// <summary>
        /// ecoSpace field
        /// </summary>
        private DefaultEcoSpace ecoSpace;

        /// <summary>
        /// locatedObjects field
        /// </summary>
        private Dictionary<IObject, IObjectInstance> locatedObjects;

        /// <summary>
        /// Initializes a new instance of the <see cref="ImportObjectsFromXml"/> class.
        /// </summary>
        /// <param name="keyAttributes">The key attributes.</param>
        public ImportObjectsFromXml(KeyAttributeDictionary keyAttributes)
            : base(keyAttributes)
        {
        }

        /// <summary>
        /// Imports the specified doc.
        /// </summary>
        /// <param name="doc">The doc.</param>
        /// <param name="targetEcoSpace">The xtarget eco space.</param>
        public void Import(IXPathNavigable doc, DefaultEcoSpace targetEcoSpace)
        {
            this.ecoSpace = targetEcoSpace;
            this.locatedObjects = new Dictionary<IObject, IObjectInstance>();
            DatablockConverter converter = new DatablockConverter(this.ecoSpace.TypeSystem);
            Datablock datablock = converter.ConvertToDatablock(doc as XmlDocument);

            // create a source EcoSpace
            DefaultEcoSpace sourceEcoSpace = Activator.CreateInstance(this.ecoSpace.GetType()) as DefaultEcoSpace;
            sourceEcoSpace.PersistenceMapper = null;
            sourceEcoSpace.Active = true;
            FillCache(sourceEcoSpace, datablock);

            this.sourceCache = sourceEcoSpace.GetEcoService<ICache>();
            this.sourceOrp = sourceEcoSpace.GetEcoService<IObjectRepresentationProvider>();

            this.stateService = this.ecoSpace.GetEcoService<IStateService>();

            Dictionary<ObjectId, IObjectInstance> sourceObjects = new Dictionary<ObjectId, IObjectInstance>();

            foreach (ObjectContents oc in datablock)
            {
                Locator loc = this.sourceCache.GetEnsuredLocatorById(oc.ObjectId);
                IObjectInstance sourceObj = this.sourceOrp.IObjectForLocator(loc);
                sourceObjects.Add(oc.ObjectId, sourceObj);
            }

            this.EnsureTargetObjects(sourceObjects);

            this.EnsureTargetLinkObjects(sourceObjects);

            this.CopyTargetAttributes(sourceObjects);

            this.CopyTargetAssociations(sourceObjects);
        }

        /// <summary>
        /// Gets the id from obj.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns>id from obj</returns>
        private static ObjectId GetIdFromObj(IObject obj)
        {
            return obj.ServiceProvider.GetEcoService<ICache>().GetIdByLocator(obj.ServiceProvider.GetEcoService<IObjectRepresentationProvider>().LocatorForIObject(obj.ObjectInstance));
        }

        /// <summary>
        /// Fills the cache.
        /// </summary>
        /// <param name="targetEcoSpace">The target eco space.</param>
        /// <param name="datablock">The datablock.</param>
        private static void FillCache(DefaultEcoSpace targetEcoSpace, Datablock datablock)
        {
            ICache targetCache = targetEcoSpace.GetEcoService<ICache>();
            while (targetCache is Eco.Cache.ChainedCacheBase)
            {
                targetCache = (targetCache as Eco.Cache.ChainedCacheBase).NextCache;
            }

            foreach (ObjectContents oc in datablock)
            {
                Eco.Cache.Locator loc = targetCache.GetEnsuredLocatorById(oc.ObjectId);
                targetCache.SetExistenceState(loc, Eco.Cache.ExistenceState.Existing);
            }

            foreach (ObjectContents oc in datablock)
            {
                Eco.Cache.Locator loc = targetCache.GetEnsuredLocatorById(oc.ObjectId);
                IClass c = targetEcoSpace.TypeSystem.AllClasses[oc.ObjectId.ClassId];
                for (int i = 0; i < oc.MemberCount; i++)
                {
                    if (oc.IsMemberAssigned(i))
                    {
                        IStructuralFeature sf = c.AllStructuralFeatures[i];

                        if ((!sf.IsDerived) && sf.Persistent)
                        {
                            object value = oc.GetMember(i);
                            IAssociationEnd ae = sf as IAssociationEnd;
                            if (ae != null)
                            {
                                if (ae.Multiplicity.IsSingle && ae.IsStoredInObject)
                                {
                                    SingleAssociationEnd sae = value as SingleAssociationEnd;
                                    Eco.Cache.Locator referredLocator = null;
                                    if ((sae != null) && (sae.Id != null))
                                    {
                                        referredLocator = targetCache.GetEnsuredLocatorById(sae.Id);
                                    }

                                    targetCache.SetCurrentAndFetchedValue(loc, sf, referredLocator);
                                    if ((sae != null) && (sae.OrderNo != -1))
                                    {
                                        targetCache.SetCurrentAndFetchedOrderNo(loc, sf, sae.OrderNo);
                                    }
                                }
                            }
                            else
                            {
                                targetCache.SetCurrentAndFetchedValue(loc, sf, value);
                            }

                            targetCache.SetMemberPersistenceState(loc, sf, Eco.Cache.PersistenceState.Current);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Copies the target associations.
        /// </summary>
        /// <param name="sourceObjects">The source objects.</param>
        private void CopyTargetAssociations(Dictionary<ObjectId, IObjectInstance> sourceObjects)
        {
            // copy embedded association ends
            foreach (ObjectId id in sourceObjects.Keys)
            {
                IObject sourceObj = sourceObjects[id];
                IObject targetObj = this.LocateTargetObject(id, sourceObj, this.ecoSpace);
                foreach (IStructuralFeature sf in sourceObj.UmlClass.AllStructuralFeatures)
                {
                    IAssociationEnd ae = sf as IAssociationEnd;
                    if (ae != null && ae.IsStoredInObject && ae.AssociationEndKind != AssociationEndKind.InnerLink)
                    {
                        if (!ae.IsDerived)
                        {
                            IObjectInstance referencedSourceObj = (sourceObj.Properties[ae] as IObjectReference).ObjectInstance;
                            if (referencedSourceObj == null || referencedSourceObj.IsNullInstance)
                            {
                                targetObj.Properties[ae].AsObject = null;
                            }
                            else
                            {
                                IObjectInstance targetRefObject = this.LocateTargetObject(GetIdFromObj(referencedSourceObj), referencedSourceObj, this.ecoSpace);
                                targetObj.Properties[ae].Assign(targetRefObject);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Copies the target attributes.
        /// </summary>
        /// <param name="sourceObjects">The source objects.</param>
        private void CopyTargetAttributes(Dictionary<ObjectId, IObjectInstance> sourceObjects)
        {
            // kopiera attribut
            foreach (ObjectId id in sourceObjects.Keys)
            {
                IObject sourceObj = sourceObjects[id];

                IObject targetObj = this.LocateTargetObject(id, sourceObj, this.ecoSpace);

                foreach (IStructuralFeature sf in sourceObj.UmlClass.AllStructuralFeatures)
                {
                    IAttribute attr = sf as IAttribute;
                    if (attr != null)
                    {
                        if (!attr.IsDerived &&
                            attr.SaveAction != SaveAction.DbAssign &&
                            (attr.SaveAction != SaveAction.Freeze || this.stateService.IsNew(targetObj)))
                        {
                            targetObj.Properties[attr].Assign(sourceObj.Properties[attr]);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Ensures the target link objects.
        /// </summary>
        /// <param name="sourceObjects">The source objects.</param>
        private void EnsureTargetLinkObjects(Dictionary<ObjectId, IObjectInstance> sourceObjects)
        {
            // create link objects
            foreach (ObjectId id in sourceObjects.Keys)
            {
                IObject sourceObj = sourceObjects[id];
                if (sourceObj.UmlClass.Association != null)
                {
                    IObjectInstance targetObj = this.LocateTargetObjectNoCreate(id, sourceObj, this.ecoSpace);
                    if (targetObj == null)
                    {
                        IAssociationEnd ae1 = sourceObj.UmlClass.AllStructuralFeatures[sourceObj.UmlClass.AllStructuralFeatures.Count - 1] as IAssociationEnd;
                        IAssociationEnd ae2 = sourceObj.UmlClass.AllStructuralFeatures[sourceObj.UmlClass.AllStructuralFeatures.Count - 2] as IAssociationEnd;

                        IObjectInstance sourceObj1 = (sourceObj.Properties[ae1] as IObjectReference).ObjectInstance;
                        IObjectInstance sourceObj2 = (sourceObj.Properties[ae2] as IObjectReference).ObjectInstance;

                        IObjectInstance targetObj1 = this.LocateTargetObject(GetIdFromObj(sourceObj1), sourceObj1, this.ecoSpace);
                        IObjectInstance targetObj2 = this.LocateTargetObject(GetIdFromObj(sourceObj2), sourceObj2, this.ecoSpace);

                        targetObj = this.ecoSpace.GetEcoService<IObjectFactoryService>().CreateNewLinkObject(targetObj1, targetObj2, sourceObj.UmlClass.Association);

                        string keyAttribute = KeyAttributes.KeyAttributeForType(targetObj.AsObject.GetType());
                        targetObj.Properties[keyAttribute].AsObject = id.Key;
                        this.locatedObjects[sourceObj] = targetObj;
                    }
                }
            }
        }

        /// <summary>
        /// Ensures the target objects.
        /// </summary>
        /// <param name="sourceObjects">The source objects.</param>
        private void EnsureTargetObjects(Dictionary<ObjectId, IObjectInstance> sourceObjects)
        {
            foreach (ObjectId id in sourceObjects.Keys)
            {
                IObject sourceObj = sourceObjects[id];
                if (sourceObj.UmlClass.Association == null)
                {
                    this.LocateTargetObject(id, sourceObj, this.ecoSpace);
                }
            }
        }

        /// <summary>
        /// Locates the target object no create.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <param name="sourceObj">The source obj.</param>
        /// <param name="targetEcoSpace">The target eco space.</param>
        /// <returns>located target object</returns>
        private IObjectInstance LocateTargetObjectNoCreate(ObjectId id, IObject sourceObj, DefaultEcoSpace targetEcoSpace)
        {
            IObjectInstance res;
            if (this.locatedObjects.TryGetValue(sourceObj, out res))
            {
                return res;
            }

            string keyAttribute = KeyAttributes.KeyAttributeForType(sourceObj.AsObject.GetType());

            IObjectList objects = targetEcoSpace.GetEcoService<IOclService>().Evaluate(
                string.Format(CultureInfo.CurrentCulture, "{0}.allInstances->select(o|o.{1} = vID)", sourceObj.UmlClass.Name, keyAttribute),
                targetEcoSpace.VariableFactory.CreateVariableList("vID", id.Key)) as IObjectList;
            if (objects.Count == 0)
            {
                return null;
            }
            else if (objects.Count == 1)
            {
                res = objects[0];
            }
            else
            {
                throw new EcoContribException(string.Format(CultureInfo.CurrentCulture, "Duplicate objects found in class {0} for id {1}", sourceObj.UmlClass.Name, id.Key));
            }

            this.locatedObjects[sourceObj] = res;
            return res;
        }

        /// <summary>
        /// Locates the target object.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <param name="sourceObj">The source obj.</param>
        /// <param name="targetEcoSpace">The target eco space.</param>
        /// <returns>located target object</returns>
        private IObjectInstance LocateTargetObject(ObjectId id, IObject sourceObj, DefaultEcoSpace targetEcoSpace)
        {
            IObjectInstance res = this.LocateTargetObjectNoCreate(id, sourceObj, targetEcoSpace);
            if (res == null)
            {
                string keyAttribute = KeyAttributes.KeyAttributeForType(sourceObj.AsObject.GetType());
                res = targetEcoSpace.GetEcoService<IObjectFactoryService>().CreateNewObject(sourceObj.UmlClass);
                Guid g = (Guid)id.Key;
                res.Properties[keyAttribute].AsObject = g;
                this.locatedObjects[sourceObj] = res;
            }

            return res;
        }
    }
}
